/*
 * sys.c - Syscalls implementation
 */

#include <utils.h>
#include <errno.h>
#include <list.h>
#include <mm.h>
#include <string.h>
#include <filesystem.h>

// SIZE_OF_BUFFER ha de ser divisor de PAGE_SIZE
#define SIZE_OF_BUFFER PAGE_SIZE/16

int sys_ni_syscall(){
  return -ENOSYS;
}

// Checks the fd for the channels of the given process
int check_fd(int fd, struct channel *channels)
{
  if (fd < 0 || fd >= NR_CHANNELS){
    return -EBADF;
  }
  if (!channels[fd].initialized){
    return -EBADF;
  }
  return 0;
}


inline int check_permissions(int flags, int access_mode)
{
  if ((flags & access_mode) == 0 || (flags & ~access_mode) != 0) {
    return -EBADF;
  }
  return 0;
}

int sys_fork(void)
{
  unsigned int address;
  struct task_struct * new;
  struct task_struct * cur;
  int i;

  // a) Buscar entrada lliure a la taula de processos
  if (list_empty(&freequeue))
    return -ENOMEM;

  new = list_head_to_task_struct(list_first(&freequeue));
  cur = current();
  list_del(&new->list);
  // NOTA: If rollback task->list to freequeue

  // b) Copiar task_union (task_struct i pila) del pare al fill
  copy_data((void*)cur, (void*)new, sizeof(unsigned long) * KERNEL_STACK_SIZE);
  // NOTA: If rollback task->stat = FREE

  // c) Herencia dades usuari -> copiar pagines de dades
  // Alloc the pages in the child process
  if (!alloc_task_pages(new)) {
    list_add_tail(&new->list, &freequeue);
    new->stat = FREE;
    return -ENOMEM;
  }
  // NOTA: If rollback free pages in child task_struct
  
  // Add the child pages to the page table after the current ones
  for (i = 0; i < NUM_PAG_DATA; i++)
  {
    set_ss_pag(PAG_LOG_INIT_DATA_P0 + NUM_PAG_DATA + i, new->data_pages[i]);
  }
  
  // Copy the user data from the parent to the child pages
  for (address = PAG_LOG_INIT_DATA_P0 * PAGE_SIZE; address < (PAG_LOG_INIT_DATA_P0 + NUM_PAG_DATA) * PAGE_SIZE; address += PAGE_SIZE) {
    copy_data((void*)address, (void*)(address + NUM_PAG_DATA * PAGE_SIZE), PAGE_SIZE);
  }

  // Remove the child pages from the page table
  for (i = 0; i < NUM_PAG_DATA; i++)
  {
    del_ss_pag(PAG_LOG_INIT_DATA_P0 + NUM_PAG_DATA + i);
  }
  set_cr3(); // Flush TLB

  // d) Assignar el pid al fill
  // e) Arreglar task_struct fill
  init_task_info(new, get_next_pid(), cur->pid);

  // Incrementar el numero de referencies als fitxers oberts
  for (i = 0; i < NR_CHANNELS; i++) {
    if (new->channels[i].initialized) {
      new->channels[i].open_file->nr_references++;
    }
  }

  // f) Afegir fill a runqueue
  list_add_tail(&new->list, &runqueue);

  // g) Arreglar eax al fill perque retorni 0
  ((union task_union*)new)->stack[KERNEL_STACK_SIZE - 10] = 0;
  
  return new->pid;
}

int sys_getpid(void)
{
  return current()->pid;
}

int sys_nice(int quantum)
{
  // Canvia el quantum del proces actual
  int prevquantum;
  if (quantum <= 0)
    return -EINVAL;
  prevquantum = (int)current()->quantum;
  current()->quantum = (unsigned int)quantum;
  return prevquantum;
}

int sys_get_stats(int pid, struct stats *st)
{
  struct task_struct* task;
  // controlem que el pid sigui > 0
  if (pid < 0){
    return -EINVAL;
  }
  // mirem que on volem escriure no sigui una adreça de kernel i que sigui d'adreça d'usuaris
  if (!access_ok(WRITE,st,sizeof(struct stats))){
    return -EFAULT;
  }
  // busquem la task struct dintre del vector de task_structs tot mirant que la task estigui bloquejada o a ready
  task = get_active_task((unsigned int)pid);
  if (task == 0) return -ESRCH;
  // copiem a usuari el struct stats
  copy_to_user((void *) &task->task_stats, (void *)st, sizeof(struct stats));

  return 0;
}

int sys_sem_init(int n_sem, unsigned int value)
{
  if (n_sem < 0 || n_sem >= NR_SEM)
    return -EINVAL;
  if (sem[n_sem].initialized)
    return -EBUSY;
  sem[n_sem].value = value;
  sem[n_sem].owner_pid = sys_getpid();
  sem[n_sem].initialized = 1;
  INIT_LIST_HEAD(&sem[n_sem].blocked_list);
  return 0;
}

int sys_sem_wait(int n_sem)
{
  struct task_struct* ts;
  if (sys_getpid() == 0)
    return -EPERM;
  if (n_sem < 0 || n_sem >= NR_SEM)
    return -EINVAL;
  if (!sem[n_sem].initialized)
    return -EINVAL;
  sem[n_sem].value--;
  if (sem[n_sem].value < 0) {
    // Block process
    ts = current();
    block_process(ts, &sem[n_sem].blocked_list);
  }
  return 0;
}

int sys_sem_signal(int n_sem)
{
  struct list_head* first;
  if (n_sem < 0 || n_sem >= NR_SEM)
    return -EINVAL;
  if (!sem[n_sem].initialized)
    return -EINVAL;
  sem[n_sem].value++;
  if (sem[n_sem].value <= 0) {
    first = list_first(&sem[n_sem].blocked_list);
    unblock_process(list_head_to_task_struct(first),0);
  }
  return 0;
}

int sys_sem_destroy(int n_sem)
{
  struct list_head* first;
  if (n_sem < 0 || n_sem >= NR_SEM)
    return -EINVAL;
  if (!sem[n_sem].initialized)
    return -EINVAL;
  if (sem[n_sem].owner_pid != sys_getpid())
    return -EPERM;
  // Desbloquejar els processos que estan esperant.
  while (!list_empty(&sem[n_sem].blocked_list)) {
    // Moure a la cua de Ready.
    first = list_first(&sem[n_sem].blocked_list);
    // Afegir a la cua de ready i fer que la crida a sem_wait del procés retorni -1.
    unblock_process(list_head_to_task_struct(first),-1);
  }
  sem[n_sem].initialized = 0;
  return 0;
}

int sys_open(const char *path, int flags)
{
  int i;
  int dir;
  int fd = -1;
  int of = -1;
  struct task_struct* ts;
  // Check parameters
  if ((path == 0) || !access_ok(READ, path, FILE_NAME_SIZE))
    return -EFAULT;
  if (flags & ~(O_CREAT|O_EXCL|O_RDWR))
    return -EINVAL;
  dir = filename_ok(path);
  if (dir < 0)
    return -ENAMETOOLONG;

  dir = find_file(path);
   // Si no existeix el fitxer
  if (dir < 0) {
    // Si esta el flag CREAT, el creem
    if ((flags & O_CREAT) != 0) {
      dir = init_new_file(path, flags);
      // Si no es pogues crear retornem l'error
      if (dir < 0) {
        return dir;
      }
    }
    // Si no és  un error
    else {
      return -ENOENT;
    }
  }
  // Si existeix pero esta el flag CREAT i EXCL dona error
  else if (((flags & O_CREAT) != 0) && ((flags & O_EXCL) != 0)) {
    return -EEXIST;
  }
  // Mirar que els permissos siguin els adients
  if (!file_permissions_ok(&directory[dir], flags)) {
    return -EACCES;
  }
  ts = current();
  // Search for a free channel
  for (i = 0; fd < 0 && i < NR_CHANNELS; i++) {
    if (!ts->channels[i].initialized) {
      fd = i;
    }
  }
  if (fd < 0) {
    return -EMFILE;
  }
  of = init_open_file(ts, fd, dir, flags);
  if (of < 0) return of;
  return fd;
}

int sys_read(int fd, char *buffer, int size)
{
  int check = 0;
  struct task_struct *ts = current();
  // Comprovació de paràmetres
  check = check_fd(fd, ts->channels);
  if (check < 0)
    return check;
  check = check_permissions(O_RDONLY, ts->channels[fd].open_file->access_mode);
  if (check < 0)
    return check;
  if (buffer == 0 || !access_ok(WRITE, buffer, size))
    return -EFAULT;
  if (size < 0)
    return -EINVAL;

  // Crida al read dependent
  return (*ts->channels[fd].open_file->file->ops.read)(fd, buffer, size);
}

int sys_dup(int fd)
{
  int i;
  int new_fd = -1;
  int check;
  struct task_struct *ts = current();
  // Check parameters 
  check = check_fd(fd, ts->channels);
  if (check < 0)
    return check;
  // Check channel to duplicate is initialized
  if(!ts->channels[fd].initialized)
    return -EBADF;
  // Search for a free channel
  for (i = 0; new_fd < 0 && i < NR_CHANNELS; i++) {
    if (!ts->channels[i].initialized) {
      new_fd = i;
    }
  }
  if (new_fd < 0)
    return -EMFILE;

  // Inicialitzar el nou canal
  ts->channels[new_fd].open_file = ts->channels[fd].open_file;
  ts->channels[new_fd].initialized = 1;
  // Actualitzar el open file
  ts->channels[fd].open_file->nr_references++;
  return new_fd;
}

int sys_write(int fd, char *buffer, int size)
{
  int check;
  struct task_struct* ts = current();

  // Comprovació de paràmetres
  check = check_fd(fd, ts->channels);
  if (check < 0)
    return check;
  check = check_permissions(O_WRONLY, ts->channels[fd].open_file->access_mode);
  if (check < 0)
    return check;
  else if (size < 0)
    return -EINVAL;
  else if (buffer == 0 || !access_ok(READ, buffer, size))
    return -EFAULT;

  // Crida al write dependent
  return (*ts->channels[fd].open_file->file->ops.write)(fd, buffer, size);
}

int sys_close(int fd)
{
  int check;
  struct task_struct *ts = current();

  // Comprovació de paràmetres
  check = check_fd(fd, ts->channels);
  if (check < 0)
    return check;

  // Actualitzar el open file
  ts->channels[fd].open_file->nr_references--;

  // Si no queda cap referència al fitxer, s'ha de tancar tot i cridar al close dependent
  if (!ts->channels[fd].open_file->nr_references) {
    (ts->channels[fd].open_file->file->ops.close)(fd);
    ts->channels[fd].open_file->initialized = 0;
  }
  
  // Tancar el canal
  ts->channels[fd].initialized = 0;
  ts->channels[fd].open_file = 0;

  return 0;
}

void sys_exit(void)
{
  int i;
  struct task_struct* ts;
  struct task_struct* tn;
  unsigned int pid = sys_getpid();
  if (pid == 0)
    return;
  
  ts = current();
  
  // Alliberar semafors
  for (i = 0; i < NR_SEM; i++) {
    if (sem[i].initialized && sem[i].owner_pid == pid){
      sys_sem_destroy(i);
    }
  }

  // Close de canals
  for (i = 0; i < NR_CHANNELS; i++) {
    sys_close(i);
  }

  // Alliberar task_struct
  list_move(&ts->list, &freequeue);
  ts->stat = FREE;

  // Alliberar pagines fisiques
  for (i = 0; i < NUM_PAG_DATA; i++)
    free_frame(ts->data_pages[i]);

  // Passar a un proces en Run
  tn = list_head_to_task_struct(list_first(&runqueue));
  tn->task_stats.cs++;
  tn->task_stats.remaining_quantum = tn->quantum;
  remaining_quantum = tn->quantum;
  task_switch((union task_union*)tn);
}

int sys_unlink(const char *path)
{
  int dir;
  if ((path == 0) || !access_ok(READ, path, FILE_NAME_SIZE))
    return -EFAULT;
  dir = filename_ok(path);
  if (dir < 0)
    return -ENAMETOOLONG;
  dir = find_file(path);
  if (dir < 0)
    return -ENOENT;
  return (*directory[dir].ops.unlink)(&directory[dir]);
}
